<?php
class Cos_Toolbox_Function
{
    const ID = '$Id: Function.php 40 2009-04-02 14:56:47Z vipzhicheng $';

    protected static $_path = array();
    protected static $_functions = array();

    private function __construct() {}

    public static function find($path)
    {
        $args = func_get_args();
        if (empty($args)) {
            return self::$_path;
        }
        foreach ($args as $arg) {
            if (is_array($arg)) {
                foreach ($arg as $value) {
                    self::addPath($value);
                }
            } elseif (is_string($arg)) {
                if (!isset(self::$_path[$arg])) {
                    if (is_dir($arg)) {
                        self::$_path[$arg] = $arg;
                    } else {
                        throw new Exception("$arg is not a directory!");
                    }
                } else {
                    throw new Exception("$arg exist!");
                }
            } else {
                return;
            }
        }
    }

    public static function call($funcRoute)
    {
        $args = func_get_args();
        unset($args[0]);
        $function = self::get($funcRoute);
        return call_user_func_array($function, $args);
    }

    public static function create($funcRoute)
    {
        $includePath = array_reverse(self::$_path);

        $funcRouteParts = explode('@', $funcRoute);
        $functionName = $funcRouteParts[0];
        $functionPath = strtr($funcRouteParts[1], '.', DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $functionName . '.php';

        if (!empty($includePath) && is_array($includePath)) {
            foreach ($includePath as $path) {
                if (is_file($path . DIRECTORY_SEPARATOR . $functionPath))
                {
                    $code = php_strip_whitespace($path . DIRECTORY_SEPARATOR . $functionPath);
                    $functionInfo = self::_parseCode($code);
                    $function = create_function($functionInfo[0], $functionInfo[1]);
                    self::$_functions[$funcRoute] = $function;
                    return $function;
                    break;
                }
            }
        } else {
            throw new Exception("function include path is null!");
        }
    }

    public static function get($funcRoute = null)
    {
        if (is_null($funcRoute)) {
            return self::$_functions;
        } else {
            if (isset(self::$_functions[$funcRoute])) {
                return self::$_functions[$funcRoute];
            } else {
                return self::create($funcRoute);
            }
        }
    }

    protected static function _parseCode($code)
    {
        $parameterList = $functionBody = '';
        $parameterListStack = array();
        $functionBodyStack = array();

        $stackPause = false; //堆栈处理暂停，默认不暂停
        $quotePause = true; //引号中断处理暂停，默认暂停
        $quoteChar = null;

        $pStartPos = $pStopPos = $fStartPos = $fStopPos = 0;
        $codeLen = strlen($code);
        for ($i = 0; $i < $codeLen; $i++ ) {
            $char = $code{$i};
            switch ($char) {
                case '(':
                    if (!$stackPause && empty($parameterList)) {
                        if (empty($parameterListStack)) {
                            $pStartPos = $i;
                        }
                        array_push($parameterListStack, $i);
                    }
                    break;
                case ')':
                    if (!$stackPause && empty($parameterList)) {
                        array_pop($parameterListStack);
                        if (empty($parameterListStack)) {
                            $pStopPos = $i;
                            $parameterList = substr($code, $pStartPos + 1, $pStopPos - $pStartPos - 1);
                        }
                    }
                    break;
                case '{':
                    if (!$stackPause && empty($functionBody)) {
                        if (empty($functionBodyStack)) {
                            $fStartPos = $i;
                        }
                        array_push($functionBodyStack, $i);
                    }
                    break;
                case '}':
                    if (!$stackPause && empty($functionBody)) {
                        array_pop($functionBodyStack);
                        if (empty($functionBodyStack)) {
                            $fStopPos = $i;
                            $functionBody = substr($code, $fStartPos + 1, $fStopPos - $fStartPos - 1);
                            break 2;
                        }
                    }
                    break;
                case '\'':
                    //same to next
                case '"';
                    if ($quotePause) {
                        $stackPause = true;
                        $quotePause = false;
                        $quoteChar = $char;
                    } elseif ($quoteChar == $char) {
                        $stackPause = false;
                        $quotePause = true;
                        $quoteChar = null;
                    }
                    break;
                case '\\':
                    $i = $i + 1;
                    continue;
                    break;
                default:
                    break;
            }
        }
        $code = null;
        return array($parameterList, $functionBody);
    }
}
